package parser

import (
  "asteval"
  "astexec"
  "bufio"
  "scanner"
)

type Parser struct {
  look   byte
  lexeme string
  scan   *scanner.Scanner
}

func (p *Parser) Parse(src *bufio.Reader) astexec.Statement {
  p.look = scanner.None
  p.scan = scanner.New(src)
  p.match(scanner.None)
  ast := p.sequence()
  p.match(scanner.Period)
  return ast
}

func (p *Parser) match(tok byte) bool {
  if tok != p.look {
    return false
  }
  p.lexeme, p.look = p.scan.Next()
  return true
}

func (p *Parser) factor() *asteval.Expression {
  // number
  if p.look == scanner.Number {
    num := p.lexeme
    p.match(scanner.Number)
    return asteval.Number(num)
  }

  // variable
  if p.look == scanner.Ident {
    nam := p.lexeme
    p.match(scanner.Ident)
    return asteval.Variable(nam)
  }

  // unary operation '-'
  if p.look == scanner.Sub {
    p.match(scanner.Sub)
    ex := p.factor()
    return asteval.Unary("-", ex)
  }

  // expression in parentheses
  if p.look == scanner.LPar {
    p.match(scanner.LPar)
    ex := p.expr()
    p.match(scanner.RPar)
    return ex
  }

  return nil
}

func (p *Parser) term() *asteval.Expression {
  ex1 := p.factor()
  for p.look == scanner.Mul || p.look == scanner.Div {
    op := p.lexeme
    p.match(p.look)
    ex2 := p.factor()
    ex1 = asteval.Binary(op, ex1, ex2)
  }
  return ex1
}

func (p *Parser) expr() *asteval.Expression {
  ex1 := p.term()
  for p.look == scanner.Add || p.look == scanner.Sub {
    op := p.lexeme
    p.match(p.look)
    ex2 := p.term()
    ex1 = asteval.Binary(op, ex1, ex2)
  }
  return ex1
}

func (p *Parser) relation() *asteval.Expression {
  ex := p.expr()
  if p.look >= scanner.Equal && p.look <= scanner.LsEq {
    op := p.lexeme
    p.match(p.look) // ?
    ex = asteval.Binary(op, ex, p.expr())
  }
  return ex
}

func (p *Parser) sequence() astexec.Statement {
  seq := astexec.NewSequence()
  st := p.statement()
  seq.Append(st)
  for p.look == scanner.Semic {
    p.match(scanner.Semic)
    seq.Append(p.statement())
  }
  return seq
}

func (p *Parser) statement() astexec.Statement {
  switch p.look {
    case scanner.Ident:
      return p.assignment()
    case scanner.Print:
      return p.printexpr()
    case scanner.Input:
      return p.inputval()
    case scanner.If:
      return p.branching()
    case scanner.While:
      return p.looping()
  }
  return nil
}

func (p *Parser) assignment() astexec.Statement {
  nm := p.lexeme
  p.match(scanner.Ident)
  p.match(scanner.Equal)
  ex := p.expr()
  return astexec.NewAssignment(nm, ex)
}

func (p *Parser) printexpr() astexec.Statement {
  p.match(scanner.Print)
  ex := p.expr()
  return astexec.NewPrint(ex)
}

func (p *Parser) inputval() astexec.Statement {
  p.match(scanner.Input)
  nm := p.lexeme
  p.match(scanner.Ident)
  return astexec.NewInput(nm)
}

func (p *Parser) branching() astexec.Statement {
  p.match(scanner.If)
  ex := p.relation()
  p.match(scanner.Then)
  st := p.sequence()
  res := astexec.NewBranching(ex, st)

  t := res
  for p.look == scanner.ElseIf {
    p.match(scanner.ElseIf)
    ex = p.relation()
    p.match(scanner.Then)
    st = p.sequence()
    v := astexec.NewBranching(ex, st)
    t.SetElse(v)
    t = v
  }

  if p.look == scanner.Else {
    p.match(scanner.Else)
    se := p.sequence()
    t.SetElse(se)
  }

  p.match(scanner.End)
  return res
}

func (p *Parser) looping() astexec.Statement {
  p.match(scanner.While)
  ex := p.relation()
  p.match(scanner.Do)
  st := p.sequence()
  p.match(scanner.End)
  return astexec.NewLooping(ex, st)
}


